package cn.wen.java.io;

import java.io.*;
import java.nio.charset.Charset;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import cn.wen.java.lang.HelperOfString;
import cn.wen.java.utils.Assert;

/**
 * Title：文件系统操作类
 * <p>
 * Description：
 * <p>
 * Copyright：Copyright (c) 2010-2012
 * 
 * @author <a href="mailto:ct_wen@qq.com">ctwen</a>, 温承瑭
 * @version Revision：1.0 Date：2010/7/21 Description：初完成。
 *          <p>
 *          Revision：1.1 Date：2010/11/22 Description：修正copyFile问题。
 *          <p>
 *          Revision：1.2 Date：2010/12/07 Description：新增：getLegalPath，修正：getDirPath、getFolderName和getFileName。
 *          <p>
 *          Revision：1.3 Date：2010/12/09 Description：关闭资源，修改异常处理……。
 *          <p>
 *          Revision：1.5 Date：2011/1/30 Description：修订多处……（重大改动，涉及所有主要功能代码）。
 *          <p>
 *          Revision：1.6 Date：2011/11/23 Description：readFile, newFile, writeFile, delFile, delDir, copyFile, copyDir, moveFile,
 *          moveDir，为使用便利，提供String 路径版，及File 对象版。
 *          <p>
 *          Revision：1.7 Date：2012/8/28 Description：统一公开的读(read)，写(write)，删(delete)，拷贝(copy)，移动(move)方法名称等优化。
 *          <p>
 */
public final class HelperOfFile {

    private static Logger log = Logger.getLogger(HelperOfFile.class);

    // ------------------------------------------------------------------------------------------
    // File read methods
    // ------------------------------------------------------------------------------------------

    /**
     * @see #read(File)
     */
    public static String read(String path) throws IOException {
	return read(path, null);
    }

    /**
     * @see #read(File, String)
     */
    public static String read(String path, String encoding) throws IOException {
	return read(new File(path), encoding);
    }

    /**
     * 读取文本文件内容，采用默认编码格式。
     * 
     * @param file 文件对象。
     * @return 返回文本文件的内容。
     * @throws IOException
     */
    public static String read(File file) throws IOException {
	return read(file, null);
    }

    /**
     * 读取文本文件内容，采用指定编码格式。
     * 
     * @param file 文件对象。
     * @param encoding 编码方式。
     * @return 返回文本文件的内容。
     * @throws IOException
     */
    public static String read(File file, String encoding) throws IOException {
	String data;
	InputStream is = new FileInputStream(file);
	if (StringUtils.isBlank(encoding)) {
	    data = IOUtils.toString(is);
	}
	else {
	    data = IOUtils.toString(is, encoding);
	}
	is.close();
	return data;
    }

    // ------------------------------------------------------------------------------------------
    // File write methods
    // ------------------------------------------------------------------------------------------

    /**
     * @see #write(File, String)
     */
    public static boolean write(String path, String data) throws IOException {
	return write(path, data, null);
    }

    /**
     * @see #write(File, String, String)
     */
    public static boolean write(String path, String data, String encoding) throws IOException {
	return write(path, data, encoding, false);
    }

    /**
     * @see #write(File, String, String, boolean)
     */
    public static boolean write(String path, String data, String encoding, boolean append) throws IOException {
	return write(new File(path), data, encoding, append);
    }

    /**
     * <pre>
     * 对指定文件重写内容，按默认编码方式写数据。若文件不存在则自动创建。
     * 注：1.重写文件时，若不指定编码，则采用默认编码。
     * 
     * demo：<code>HelperOfFile.write(file, data);</code>
     * </pre>
     * 
     * @param file 文件对象。
     * @param data 内容。
     * @return 是否创建成功。
     * @throws IOException
     */
    public static boolean write(File file, String data) throws IOException {
	return write(file, data, null);
    }

    /**
     * <pre>
     * 对指定文件重写内容，按指定编码方式写数据。若文件不存在则自动创建。
     * 注：1.重写文件时，若不指定编码，则采用默认编码。
     * 
     * demo：<code>HelperOfFile.write(file, data, "UTF-8");</code>
     * </pre>
     * 
     * @param file 文件对象。
     * @param data 内容。
     * @param encoding 编码方式 例如 GBK 或者 UTF-8等。
     * @return 是否创建成功。
     * @throws IOException
     */
    public static boolean write(File file, String data, String encoding) throws IOException {
	return write(file, data, encoding, false);
    }

    /**
     * <pre>
     * 对指定文件重写或追加内容，按指定编码方式写数据。若文件不存在则自动创建。
     * 注：1.重写文件时，若不指定编码，则采用默认编码。
     *     2.追加内容时，编码格式无效，因此无需指定。
     * 
     * 追加：HelperOfFile.write(file, data, null, true);
     * 
     * 重写：HelperOfFile.write(file, data, "UTF-8", false);
     * </pre>
     * 
     * @param file 文件对象。
     * @param data 内容。
     * @param encoding 编码方式 例如 GBK 或者 UTF-8等。
     * @param append 是否在文件末尾追加。
     * @return 是否创建成功。
     * @throws IOException
     */
    public static boolean write(File file, String data, String encoding, boolean append) throws IOException {
	Writer out = null;
	if (append) {
	    out = new OutputStreamWriter(new FileOutputStream(file, append));
	}
	else {
	    if (StringUtils.isBlank(encoding)) encoding = Charset.defaultCharset().toString();
	    out = new OutputStreamWriter(new FileOutputStream(file), encoding);
	}
	out = new BufferedWriter(out);
	out.write(data);
	HelperOfIO.closes(out);
	return true;
    }

    /**
     * 将数据写入文件。
     * 
     * @param file 文件对象。
     * @param data 内容。
     * @return
     * @throws IOException
     */
    public static boolean write(File file, byte[] data) throws IOException {
	InputStream is = new BufferedInputStream(new ByteArrayInputStream(data));
	OutputStream os = new BufferedOutputStream(new FileOutputStream(file));
	HelperOfIO.copy(is, os, true);
	return false;
    }

    // ------------------------------------------------------------------------------------------
    // File delete methods
    // ------------------------------------------------------------------------------------------

    /**
     * @see #delete(File)
     */
    public static boolean delete(String path) {
	return delete(new File(path));
    }

    /**
     * eq: delete(file, null);
     * 
     * @param file 文件对象
     * @return 是否删除成功。
     * @see #delete(File, FileFilter)
     */
    public static boolean delete(File file) {
	return delete(file, null);
    }

    /**
     * <pre>
     * 删除具有可写权限的文件或目录——注：此方法可将物理文件或目录内容及其子目录内容删除，请斟酌使用。
     * 若是目录，则以文件过滤器筛选对象，符合条件的对象将被删除。
     * </pre>
     * 
     * @param file 文件对象
     * @param filter 文件过滤器（可以为空，侧不过滤。）
     * @return 是否删除成功。
     */
    public static boolean delete(File file, FileFilter filter) {
	if (!file.exists()) return true;

	if (file.isFile()) { // 若是文件，则以文件方式删除。
	    return delFile(file);
	}

	return deleteRecursive(file, filter); // 文件夹需要递归删除。
    }

    /**
     * 递归删除……
     */
    private static boolean deleteRecursive(File file, FileFilter filter) {
	File[] files = (null == filter) ? file.listFiles() : file.listFiles(filter);

	for (File tmp : files) {
	    if (tmp.isFile()) {
		delFile(tmp); // 删除 文件
	    }
	    else {
		deleteRecursive(tmp, filter); // 目录 递归……
	    }
	}

	return delDir(file); // 删除目录本身
    }

    /**
     * 删除文件。
     */
    private static boolean delFile(File file) {
	boolean rs = file.delete();
	String path = file.getAbsolutePath();
	if (rs) {
	    log.debug("File deleted : " + path);
	}
	else {
	    log.warn("File delete failed! - " + path);
	}
	return rs;
    }

    /**
     * 删除目录。
     */
    private static boolean delDir(File file) {
	boolean rs = false;
	String path = file.getAbsolutePath();

	if (file.list().length > 0) {
	    log.debug("Can not be deleted, because the non-empty directory. -> " + path);
	    return rs;
	}

	rs = file.delete();
	if (rs) {
	    log.debug("Directory deleted : " + path);
	}
	else {
	    log.warn("Directory delete failed! - " + path);
	}

	return rs;
    }

    // ------------------------------------------------------------------------------------------
    // File copy methods
    // ------------------------------------------------------------------------------------------

    /**
     * @see #copy(File, File)
     */
    public static long copy(String src, String dst) throws IOException {
	return copy(new File(src), new File(dst));
    }

    /**
     * eq: copy(src, null, dst);
     * 
     * @param src 源[文件、目录]
     * @param dst 目标[文件、目录]
     * @return 拷贝量
     * @throws IOException
     * @see #copy(File, FileFilter, File)
     */
    public static long copy(File src, File dst) throws IOException {
	return copy(src, null, dst);
    }

    /**
     * <pre>
     * 将源中具有可读权限的内容复制到目标里。
     * 提示： 1.若是目录，则以文件过滤器筛选对象，符合条件的对象将被复制。
     *       2.若目标路径不存在，则自动创建。
     * </pre>
     * 
     * @param src 源[文件、目录]
     * @param filter 文件过滤器（可以为空，侧不过滤。）
     * @param dst 目标[文件、目录]
     * @return 拷贝量
     * @throws IOException
     */
    public static long copy(File src, FileFilter filter, File dst) throws IOException {
	if (src.isFile()) {
	    return copyFile(src, dst);
	}
	else {
	    dst.mkdirs(); // 若目标目录不存在则新建
	}
	return copyRecursive(src, filter, dst);
    }

    /**
     * 递归复制……
     */
    private static long copyRecursive(File src, FileFilter filter, File dst) throws IOException {
	long len = 0;
	File[] files = (null == filter) ? src.listFiles() : src.listFiles(filter);

	for (File newSrc : files) {
	    File newDst = new File(dst, newSrc.getName());
	    if (newSrc.isFile()) {
		len += copyFile(newSrc, newDst); // 复制 文件
	    }
	    else {
		mkdir(newDst); // 在目标中新建文件夹
		len += copyRecursive(newSrc, filter, newDst); // 目录递归……
	    }
	}

	return len;
    }

    /**
     * 复制文件。
     */
    private static long copyFile(File src, File dst) throws IOException {
	InputStream in = new BufferedInputStream(new FileInputStream(src));
	OutputStream out = new BufferedOutputStream(new FileOutputStream(dst));
	long len = HelperOfIO.copy(in, out, true);
	log.debug(HelperOfString.format("File copied : {0}({1})", dst.getAbsolutePath(), HelperOfIO.makeSize(len)));
	return len;
    }

    // ------------------------------------------------------------------------------------------
    // File move methods
    // ------------------------------------------------------------------------------------------

    /**
     * @see #move(File, File)
     */
    public static boolean move(String src, String dst) throws IOException {
	return move(new File(src), new File(dst));
    }

    /**
     * eq: move(src, null, dst);
     * 
     * @param src 源[文件、目录]
     * @param dst 目标[文件、目录]
     * @return 是否移动成功。
     * @throws IOException
     */
    public static boolean move(File src, File dst) throws IOException {
	return move(src, null, dst);
    }

    /**
     * <pre>
     * 将源中具有可读权限的内容移动到目标里。
     * 提示： 1.若是目录，则以文件过滤器筛选对象，符合条件的对象将被移动。
     *       2.若目标路径不存在，则自动创建。
     * </pre>
     * 
     * @param src 源[文件、目录]
     * @param filter 文件过滤器（可以为空，侧不过滤。）
     * @param dst 目标[文件、目录]
     * @return 是否移动成功。
     * @throws IOException
     */
    public static boolean move(File src, FileFilter filter, File dst) throws IOException {
	copy(src, filter, dst);
	return delete(src, filter);
    }

    // ------------------------------------------------------------------------------------------
    // File size methods
    // ------------------------------------------------------------------------------------------

    /**
     * @see #getMakeSize(File)
     */
    public static String getMakeSize(String path) {
	return getMakeSize(new File(path));
    }

    /**
     * <pre>
     * 文件或目录的伪大小 。
     * 
     * 文件格式如：File size:\t路径(带容量单位的大小)
     * 目录格式如：Directory size:\t路径(带容量单位的大小)
     * </pre>
     */
    public static String getMakeSize(File file) {
	Assert.isNotNull(file, "No File specified");

	String str = file.isFile() ? "File" : "Directory";
	str += " size : {0}({1})";

	return HelperOfString.format(str, file.getAbsolutePath(), HelperOfIO.makeSize(getSize(file)));
    }

    /**
     * @see #getSize(File)
     */
    public static long getSize(String path) {
	return getSize(new File(path));
    }

    /**
     * @see #getSize(File, FileFilter)
     */
    public static long getSize(File file) {
	return getSize(file, null);
    }

    /**
     * 计算文件或目录的字节数
     */
    public static long getSize(File file, FileFilter filter) {
	Assert.isNotNull(file, "No File specified");
	if (file.isFile()) return file.length();

	// 方法返回结果
	long size = 0;

	// 获取本目录下的所有内容。
	File[] files = (null == filter) ? file.listFiles() : file.listFiles(filter);
	// 遍历累加目录下的文件大小以及子目录大小 。
	for (File tmp : files) {
	    size += getSize(tmp);
	}

	return size;
    }

    // ------------------------------------------------------------------------------------------
    // File other methods
    // ------------------------------------------------------------------------------------------

    /**
     * 提取文件夹名
     * 
     * @param path 目录
     * @return 文件名
     */
    public static String getFolderName(String path) {
	if (path.lastIndexOf(".") > path.lastIndexOf(File.separator)) {
	    path = getDirPath(path); // 此路径指定到文件，必需截取路径。
	}
	if (path.indexOf(File.separator) > 0) {
	    path = path.substring(path.lastIndexOf(File.separator) + 1);
	}
	return path;
    }

    /**
     * 提取文件路径
     * 
     * @param path 目录
     * @return 文件目录
     * @see org.apache.commons.io.FilenameUtils#getFullPathNoEndSeparator(path)
     */
    public static String getDirPath(String path) {
	if (path.lastIndexOf(".") < path.lastIndexOf(File.separator)) return path; // 此路径就是目录，而非指定到文件。

	Assert.isTrue(path.indexOf(File.separator) > 0, "不合法的路径：" + path);
	return path.substring(0, path.lastIndexOf(File.separator));
    }

    /**
     * @see #mkdir(File)
     */
    public static boolean mkdir(String path) {
	return mkdir(new File(path));
    }

    /**
     * 新建目录
     * 
     * @param path 目录
     * @return 是否创建成功。
     */
    public static boolean mkdir(File path) {
	boolean rs = path.mkdir();
	if (rs) {
	    log.debug("Directory created : " + path.getAbsolutePath());
	}
	else {
	    log.warn("Directory create failed! - " + path.getAbsolutePath());
	}
	return rs;
    }

    /**
     * @see #mkdirs(File)
     */
    public static boolean mkdirs(String path) {
	return mkdirs(new File(path));
    }

    /**
     * 新建目录（可深层）
     * 
     * @param path 目录
     * @return 是否创建成功。
     */
    public static boolean mkdirs(File path) {
	Assert.isFalse(path.isFile(), "不允许在同级目录下创建与已存在的同文件名的文件夹。");

	// isDirectory 此判断还包含测试目录是否存在，若目录存在则返回true，反之false，但还有另一种情况返回false：指定的路径是明确到文件时。
	if (path.isDirectory()) return true;

	boolean rs = path.mkdirs();
	if (rs) {
	    log.debug("Directory created : " + path.getAbsolutePath());
	}
	else {
	    log.warn("Directory create failed! - " + path.getAbsolutePath());
	}
	return rs;
    }

    /**
     * 判断路径是否存在文件系统中。
     * 
     * @param path 路径
     * @return 存在：true，反之：false。
     */
    public static boolean exists(String path) {
	return new File(path).exists();
    }

    /**
     * 判断目标文件是否存在文件系统中。
     * <p>
     * 注： 此判断还包含测试文件是否存在，若文件存在则返回true，反之false，但还有另一种情况返回false：指定的路径是明确到已存在的目录时。
     * 
     * @param path 路径
     * @return 存在：true，反之：false。
     */
    public static boolean isFile(String path) {
	return new File(path).isFile();
    }

    /**
     * 判断目标目录是否存在文件系统中。
     * <p>
     * 注： 此判断还包含测试目录是否存在，若目录存在则返回true，反之false，但还有另一种情况返回false：指定的路径是明确到已存在的文件时。
     * 
     * @param path 路径
     * @return 存在：true，反之：false。
     */
    public static boolean isDirectory(String path) {
	return new File(path).isDirectory();
    }

    /**
     * 获取符合操作系统的路径
     * 
     * @param path 路径
     * @return
     */
    public static String getAbsolutePath(String path) {
	return new File(path).getAbsolutePath();
    }

}
